﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
using SlimDxControlExtention;

namespace SampleFramework.Utilities
{
    public class PreProjectionSplitter
    {
        private Vector3 farLT = new Vector3();
        private Vector3 farLB = new Vector3();
        private Vector3 farRT = new Vector3();
        private Vector3 farRB = new Vector3();

        private Vector3 nearLT = new Vector3();
        private Vector3 nearLB = new Vector3();
        private Vector3 nearRT = new Vector3();
        private Vector3 nearRB = new Vector3();

        private Vector3 farVertStep;
        private Vector3 farHorStep;
        private Vector3 nearVertStep;
        private Vector3 nearHorStep;

        private int width, height;

        public void LoadCamera(Camera cam, int width, int height)
        {
            float z = 1;
            float y = (float)Math.Tan(cam.FieldOfView / 2);
            float x = y * cam.AspectRatio;
            float far = cam.FarPlane;
            float near = cam.NearPlane;

            this.width = width;
            this.height = height;

            farLT = new Vector3(-x, y, z) * far;
            farLB = new Vector3(-x, -y, z) * far;
            farRB = new Vector3(x, -y, z) * far;
            farRT = new Vector3(x, y, z) * far;

            farVertStep = (farLT - farLB) / height;
            farHorStep = (farRT - farLT) / width;

            nearLT = new Vector3(-x, y, z) * near;
            nearLB = new Vector3(-x, -y, z) * near;
            nearRB = new Vector3(x, -y, z) * near;
            nearRT = new Vector3(x, y, z) * near;

            nearVertStep = (nearLT - nearLB) / height;
            nearHorStep = (nearRT - nearLT) / width;
        }

        protected Matrix GetFullPlaneMatrix()
        {
            Matrix m = new Matrix();
            /*m.M11 = farLT.X; m.M12 = farLT.Y; m.M13 = farLT.Z; m.M14 = 1;
            m.M21 = farLB.X; m.M22 = farLB.Y; m.M23 = farLB.Z; m.M24 = 1;
            m.M31 = farRB.X; m.M32 = farRB.Y; m.M33 = farRB.Z; m.M34 = 1;*/
            m.set_Rows(0, new Vector4(farLB, 1));
            m.set_Rows(1, new Vector4(farLT, 1));
            m.set_Rows(2, new Vector4(farRB, 1));
            //m.set_Rows(3,new Vector4(farRT,1));
            m.set_Rows(3, new Vector4(0, 0, 0, 1));
            return m;
        }

        protected Matrix GetSubPlaneMatrix(int row, int col)
        {
            if (row < 0 || row > (height - 1)) throw new ArgumentException("row");
            if (col < 0 || col > (width - 1)) throw new ArgumentException("col");

            Matrix m = new Matrix();
            m.set_Rows(0, new Vector4(farLB + farHorStep * col + farVertStep * row, 1));
            m.set_Rows(1, new Vector4(farLB + farHorStep * col + farVertStep * (row + 1), 1));
            m.set_Rows(2, new Vector4(farLB + farHorStep * (col + 1) + farVertStep * row, 1));
            //m.set_Rows(3, new Vector4(farLB+farHorStep*(col+1)+farVertStep*(row+1), 1));
            m.set_Rows(3, new Vector4(0, 0, 0, 1));
            return m;
        }

        public Matrix GetPreProjection(int row, int col)
        {
            Matrix subPlane = GetSubPlaneMatrix(row, col);
            Matrix fullPlane = GetFullPlaneMatrix();

            //Trace.WriteLine("FullPlane:\n " + PrintMatrix(fullPlane));
            //Trace.WriteLine("SubPlane:\n " + PrintMatrix(subPlane));
            if (subPlane.Determinant() == 0)
                throw new Exception("Determinant is 0!");

            Matrix invertedSubPlane = subPlane;
            invertedSubPlane.Invert();
//            Trace.WriteLine("Inverted SubPlane:\n " + PrintMatrix(invertedSubPlane));

            Matrix transform = invertedSubPlane * fullPlane;
  //          Trace.WriteLine("PreProjection:\n " + PrintMatrix(transform));
            return transform;
        }

        public static String PrintMatrix(Matrix x)
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < 4; i++)
                builder.AppendLine(x.get_Rows(i).ToString());
            return builder.ToString();
        }
    }
}
